/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.jasper.xmlparser;

import java.io.IOException;
import java.io.InputStream;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.jasper.Constants;
import org.apache.jasper.JasperException;
import org.apache.jasper.compiler.Localizer;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.w3c.dom.Comment;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

/**
 * XML parsing utilities for processing web application deployment descriptor
 * and tag library descriptor files. FIXME - make these use a separate class
 * loader for the parser to be used.
 * 
 * @author Craig R. McClanahan
 * @version $Id: ParserUtils.java 939520 2010-04-30 00:14:14Z kkolinko $
 */

public class ParserUtils {

	/**
	 * An error handler for use when parsing XML documents.
	 */
	static ErrorHandler errorHandler = new MyErrorHandler();

	/**
	 * An entity resolver for use when parsing XML documents.
	 */
	static EntityResolver entityResolver = new MyEntityResolver();

	// Turn off for JSP 2.0 until switch over to using xschema.
	public static boolean validating = false;

	// --------------------------------------------------------- Public Methods

	/**
	 * Parse the specified XML document, and return a <code>TreeNode</code> that
	 * corresponds to the root node of the document tree.
	 * 
	 * @param uri
	 *            URI of the XML document being parsed
	 * @param is
	 *            Input source containing the deployment descriptor
	 * 
	 * @exception JasperException
	 *                if an input/output error occurs
	 * @exception JasperException
	 *                if a parsing error occurs
	 */
	public TreeNode parseXMLDocument(String uri, InputSource is)
			throws JasperException {

		Document document = null;

		// Perform an XML parse of this document, via JAXP
		try {
			DocumentBuilderFactory factory = DocumentBuilderFactory
					.newInstance();
			factory.setNamespaceAware(true);
			factory.setValidating(validating);
			DocumentBuilder builder = factory.newDocumentBuilder();
			builder.setEntityResolver(entityResolver);
			builder.setErrorHandler(errorHandler);
			document = builder.parse(is);
		} catch (ParserConfigurationException ex) {
			throw new JasperException(Localizer.getMessage(
					"jsp.error.parse.xml", uri), ex);
		} catch (SAXParseException ex) {
			throw new JasperException(Localizer.getMessage(
					"jsp.error.parse.xml.line", uri, Integer.toString(ex
							.getLineNumber()), Integer.toString(ex
							.getColumnNumber())), ex);
		} catch (SAXException sx) {
			throw new JasperException(Localizer.getMessage(
					"jsp.error.parse.xml", uri), sx);
		} catch (IOException io) {
			throw new JasperException(Localizer.getMessage(
					"jsp.error.parse.xml", uri), io);
		}

		// Convert the resulting document to a graph of TreeNodes
		return (convert(null, document.getDocumentElement()));
	}

	/**
	 * Parse the specified XML document, and return a <code>TreeNode</code> that
	 * corresponds to the root node of the document tree.
	 * 
	 * @param uri
	 *            URI of the XML document being parsed
	 * @param is
	 *            Input stream containing the deployment descriptor
	 * 
	 * @exception JasperException
	 *                if an input/output error occurs
	 * @exception JasperException
	 *                if a parsing error occurs
	 */
	public TreeNode parseXMLDocument(String uri, InputStream is)
			throws JasperException {

		return (parseXMLDocument(uri, new InputSource(is)));
	}

	// ------------------------------------------------------ Protected Methods

	/**
	 * Create and return a TreeNode that corresponds to the specified Node,
	 * including processing all of the attributes and children nodes.
	 * 
	 * @param parent
	 *            The parent TreeNode (if any) for the new TreeNode
	 * @param node
	 *            The XML document Node to be converted
	 */
	protected TreeNode convert(TreeNode parent, Node node) {

		// Construct a new TreeNode for this node
		TreeNode treeNode = new TreeNode(node.getNodeName(), parent);

		// Convert all attributes of this node
		NamedNodeMap attributes = node.getAttributes();
		if (attributes != null) {
			int n = attributes.getLength();
			for (int i = 0; i < n; i++) {
				Node attribute = attributes.item(i);
				treeNode.addAttribute(attribute.getNodeName(), attribute
						.getNodeValue());
			}
		}

		// Create and attach all children of this node
		NodeList children = node.getChildNodes();
		if (children != null) {
			int n = children.getLength();
			for (int i = 0; i < n; i++) {
				Node child = children.item(i);
				if (child instanceof Comment)
					continue;
				if (child instanceof Text) {
					String body = ((Text) child).getData();
					if (body != null) {
						body = body.trim();
						if (body.length() > 0)
							treeNode.setBody(body);
					}
				} else {
					TreeNode treeChild = convert(treeNode, child);
				}
			}
		}

		// Return the completed TreeNode graph
		return (treeNode);
	}
}

// ------------------------------------------------------------ Private Classes

class MyEntityResolver implements EntityResolver {

	public InputSource resolveEntity(String publicId, String systemId)
			throws SAXException {
		for (int i = 0; i < Constants.CACHED_DTD_PUBLIC_IDS.length; i++) {
			String cachedDtdPublicId = Constants.CACHED_DTD_PUBLIC_IDS[i];
			if (cachedDtdPublicId.equals(publicId)) {
				String resourcePath = Constants.CACHED_DTD_RESOURCE_PATHS[i];
				InputStream input = this.getClass().getResourceAsStream(
						resourcePath);
				if (input == null) {
					throw new SAXException(Localizer.getMessage(
							"jsp.error.internal.filenotfound", resourcePath));
				}
				InputSource isrc = new InputSource(input);
				return isrc;
			}
		}
		Log log = LogFactory.getLog(MyEntityResolver.class);
		if (log.isDebugEnabled())
			log.debug("Resolve entity failed" + publicId + " " + systemId);
		log.error(Localizer.getMessage("jsp.error.parse.xml.invalidPublicId",
				publicId));
		return null;
	}
}

class MyErrorHandler implements ErrorHandler {

	public void warning(SAXParseException ex) throws SAXException {
		Log log = LogFactory.getLog(MyErrorHandler.class);
		if (log.isDebugEnabled())
			log.debug("ParserUtils: warning ", ex);
		// We ignore warnings
	}

	public void error(SAXParseException ex) throws SAXException {
		throw ex;
	}

	public void fatalError(SAXParseException ex) throws SAXException {
		throw ex;
	}
}